/*
	A set of classes to display and select previously-defined meshes and correspondences.
 
 */


const SELECTED_FILL = "rgba(255, 0, 0, 0.75)";
const SELECTED_STROKE = "rgba(255, 100, 100, 1)";
const UNSELECTED_FILL = "rgba(0, 0, 0, 0.25)";
const UNSELECTED_STROKE = "rgba(255, 255, 255, 1)";
const POINT_LINE_WIDTH = 2;
const POLY_LINE_WIDTH = 2;

const REPROJECTED_FILL = "rgba(255, 255, 255, 0.25)";
const REPROJECTED_STROKE = "rgba(0, 0, 0, 1)";
const REPROJECTED_LINE_WIDTH = 1;

const BAD_ERROR_COLOR = [200, 0, 0];
const GOOD_ERROR_COLOR = [0, 200, 0];

function Modify2D() {
	this.points = new Array();
	this.reprojPoints = new Array();
	this.triangles = new Array();
	this.meshSelected = new Array();
	
	var canvas = document.getElementById('canvasObject');
	this.canvasOffset = findPos(canvas);
}

// adds a point to the canvas.
Modify2D.prototype.addPoint = function(index, u, v, reprojU, reprojV, error, single){
	var point = new Point2D(u,v,false);
	point.single = single;
	point.setStyle(UNSELECTED_FILL, UNSELECTED_STROKE, UNSELECTED_STROKE, POINT_LINE_WIDTH)
	point.selected = false;
	point.parentMeshes = new Array();

	var reprojPoint = new Point2D(reprojU, reprojV, false)
	point.error = error;
	reprojPoint.setStyle(REPROJECTED_FILL, REPROJECTED_STROKE, REPROJECTED_STROKE, REPROJECTED_LINE_WIDTH)
	
	this.points[index] = point;
	this.reprojPoints[index] = reprojPoint;
}

// adds a triangle to the canvas.
Modify2D.prototype.addTriangle = function(mapping, triangles, index){
	var tri = new Polygon2D();
	tri.meshIndex = index;
	this.meshSelected[index] = false;
	for (var i = 0; i < mapping.length; i++){
		var point = this.points[mapping[i]]
		tri.addPoint(point);
		point.parentMeshes[point.parentMeshes.length] = index;
	}
	
	for (var i = 0; i < triangles.length; i = i + 3){
		tri.addTriangle(triangles[i], triangles[i+1], triangles[i+2]);
	}
	tri.setStyle(UNSELECTED_STROKE, UNSELECTED_FILL, POLY_LINE_WIDTH)
	this.triangles[this.triangles.length] = tri;
	this.toggleCanvasMeshSelection(index);
}

// draws the image and the polygon/point.
Modify2D.prototype.drawEverything = function(){
	drawImage();
	
	for (var i = 0; i < this.triangles.length; i++){
		this.triangles[i].draw();
	}
	for (var i in this.points){
		this.points[i].draw()
		if (this.points[i].selected){
			this.drawReprojection(i);
		}
	}
}

// draws a circle around the point whose radius is the error.
Modify2D.prototype.drawReprojection = function(index){
	var original = this.points[index];
	var reproj = this.reprojPoints[index];
	
	var du = original.u - reproj.u;
	var dv = original.v - reproj.v;
	var dist = Math.sqrt(du*du + dv*dv);
	
	var ctx = getContext();
	ctx.strokeStyle = REPROJECTED_STROKE;
	ctx.lineWidth = REPROJECTED_LINE_WIDTH;
	ctx.beginPath();
	ctx.arc(original.u, original.v, dist, 0, Math.PI*2, true);
	ctx.closePath();
	ctx.stroke();
	
}

// handles mouse click events.  If you click a point,
// the point is selected and the corresponding checkbox is checked.
// If you click a mesh, the mesh and all points in the mesh are
// selected.  If you don't click on anything, nothing is selected.
Modify2D.prototype.click = function(ev,fx,fy){
	var mousePoint = mousePos(ev, "canvasObject", this.canvasOffset.x, this.canvasOffset.y);
	var canvas = document.getElementById('imagepanel')
	var x = mousePoint.x + canvas.scrollLeft;
	x=fx*x;
	var y = mousePoint.y + canvas.scrollTop;
	y=fy*y;
	var selectedI = null;
	
	//alert(x+" "+y);
	
	// unselect all of the triangles.
	this.unselectAllMeshes()
	
	// find out what point, if any, we clicked.
	for (var i in this.points){
		var point = this.points[i];
		if (point.onXY(x,y)){
			this.selectPoint(i);
			selectedI = i;
		} else if (point.selected) {
			this.unselectPoint(i);
		}
	}
	
	var selectedPoint = null;
	if (selectedI != null){
		selectedPoint = this.points[selectedI];
	}
	
	// make sure we're not clicking on a disabled box.
	if (selectedPoint != null && ! selectedPoint.single){
		// find the first parent mesh.
		var meshIndex = selectedPoint.parentMeshes[0];
		if (!document.getElementById('mesh' + meshIndex).disabled){
			this.selectMesh(meshIndex);
		} else {
			this.unselectPoint(selectedI)
		}
		
	} else if (selectedPoint != null && selectedPoint.single){
		if (document.getElementById('corr' + selectedI).disabled){
			this.unselectPoint(selectedI);
		}
	}
	
	this.drawEverything();
}

// selects a point.
Modify2D.prototype.selectPoint = function(index){
	var point = this.points[index];
	point.selected = true;
	point.setStyle(SELECTED_FILL, SELECTED_STROKE, SELECTED_STROKE, POINT_LINE_WIDTH)
	var docEl = document.getElementById('corr' + index);
	if (docEl){
		docEl.checked = true;
	}
}

// unselects a point.
Modify2D.prototype.unselectPoint = function(index){
	var point = this.points[index];
	point.selected = false;
	point.setStyle(UNSELECTED_FILL, UNSELECTED_STROKE, UNSELECTED_STROKE, POINT_LINE_WIDTH)
	var docEl = document.getElementById('corr' + index);
	if (docEl){
		docEl.checked = false;
	}
}

// selects a mesh.
Modify2D.prototype.selectMesh = function(index){
	document.getElementById('mesh' + index).checked = true;
	this.meshSelected[index] = true;
	for (var i in this.triangles){
		var tri = this.triangles[i];
		if (tri.meshIndex == index){
			tri.setStyle(SELECTED_STROKE, SELECTED_FILL, POLY_LINE_WIDTH)
		}
	}
	
	this.selectPointsFromMesh(index);
}

// unselects a mesh.
Modify2D.prototype.unselectMesh = function(index){
	document.getElementById('mesh' + index).checked = false;
	this.meshSelected[index] = false;
	for (var i in this.triangles){
		var tri = this.triangles[i];
		if (tri.meshIndex == index){
			tri.setStyle(UNSELECTED_STROKE, UNSELECTED_FILL, POLY_LINE_WIDTH)
		}
	}
	
	this.selectPointsFromMesh(index);
}

// given a mesh index, this selects all the points in the mesh.
Modify2D.prototype.selectPointsFromMesh = function(index){
	for (var i in this.points){
		var parents = this.points[i].parentMeshes;
		if (parents.length > 0){
			this.unselectPoint(i);
		
			for (var j in parents){
				var meshIndex = parents[j]
				if (meshIndex in this.meshSelected && this.meshSelected[meshIndex]){
					this.selectPoint(i);
					break;
				}
			}
		}
	}
}

// unselects all meshes.
Modify2D.prototype.unselectAllMeshes = function(){
	for (var i in this.triangles){
		var tri = this.triangles[i];
		document.getElementById('mesh' + tri.meshIndex).checked = false;
		this.meshSelected[tri.meshIndex] = false;
		tri.setStyle(UNSELECTED_STROKE, UNSELECTED_FILL, POLY_LINE_WIDTH)
	}
}

// unselects all points.
Modify2D.prototype.unselectAllPoints = function(){
	for (var i in this.points){
		if (this.points[i].single){
			this.unselectPoint(i);
		}
	}
}

// toggles if the mesh given by "index" is selected or not.
Modify2D.prototype.toggleCanvasMeshSelection = function(index){
	var selected = document.getElementById('mesh' + index).checked;
	if (selected){
		this.selectMesh(index);
	} else{
		this.unselectMesh(index);
	}
	this.drawEverything();
}

// toggles if the point given by "index" is selected or not.
Modify2D.prototype.toggleCanvasPointSelection = function(index){
	var selected = document.getElementById('corr' + index).checked;
	if (selected){
		this.selectPoint(index);
	} else{
		this.unselectPoint(index);
	}
	this.drawEverything();
}

// dumps out which polygons and which points are selected.
Modify2D.prototype.dumpToHTML = function(){
	var pointSelectString = ''
	var meshSelectString = ''
	for (var i in this.meshSelected){
		if (this.meshSelected[i]){
			meshSelectString += i + ' '
		}
	}
	
	for (var i in this.points){
		if (this.points[i].selected && this.points[i].single){
			pointSelectString += i + ' '
		}
	}

	document.getElementById('meshesSelected').value = meshSelectString;
	document.getElementById('pointsSelected').value = pointSelectString;
	if (meshSelectString == '' && pointSelectString == ''){
		document.getElementById('warning').innerHTML = 'First select some meshes or points.'
		return false;
	}else{
		return true;
	}
}


// fills in some information about the mesh's average error and colors
// each value in the tables.
Modify2D.prototype.fillInErrors = function(){
	
	var meshErrors = new Array();
	var meshCounts = new Array();
	
	var maxSinglePointError = undefined;
	var minSinglePointError = undefined;
	var maxMeshError = undefined;
	var minMeshError = undefined;
	
	// find out the sum of all errors across a mesh so as to get the average.
	for (var i in this.points){
		var point = this.points[i]
		if (maxSinglePointError == undefined || point.error > maxSinglePointError)
			maxSinglePointError = point.error;
		if (minSinglePointError == undefined || point.error < minSinglePointError)
			minSinglePointError = point.error;
		
		
		for (var j in point.parentMeshes){
			var index = point.parentMeshes[j];
			if (typeof(meshCounts[index]) == 'undefined'){
				meshErrors[index] = point.error
				meshCounts[index] = 1
			} else {
				meshErrors[index] = meshErrors[index] + point.error
				meshCounts[index] = meshCounts[index] + 1;
			}
		}
	}
	
	// find the max/min mesh error.
	for (var i in this.meshSelected){
		var error = meshErrors[i] / meshCounts[i]
		if (maxMeshError == undefined || error > maxMeshError)
			maxMeshError = error;
		if (minMeshError == undefined || error < minMeshError)
			minMeshError = error;
	}
	
	for (var i in this.meshSelected){
		// interpolate to find the color of this label.
		var error = meshErrors[i] / meshCounts[i]
		if (minMeshError == maxMeshError){
			var alpha = 0;
		} else{
			var alpha = (error - minMeshError) / (maxMeshError - minMeshError);
		}
		var colorR = Math.round(alpha * BAD_ERROR_COLOR[0] + (1-alpha) * GOOD_ERROR_COLOR[0])
		var colorG = Math.round(alpha * BAD_ERROR_COLOR[1] + (1-alpha) * GOOD_ERROR_COLOR[1])
		var colorB = Math.round(alpha * BAD_ERROR_COLOR[2] + (1-alpha) * GOOD_ERROR_COLOR[2])
		
		document.getElementById('mesh' + i + 'error').innerHTML = Math.round(10 * meshErrors[i] / meshCounts[i]) / 10;
		document.getElementById('mesh' + i + 'error').style.color = 'rgb(' + colorR + ',' + colorG + ',' + colorB + ')';
	}
	
	// color in the values in the table of points.
	for (var i in this.points){
		var point = this.points[i]
		if (point.single){
			var error = point.error
			if (minSinglePointError == maxSinglePointError){
				var alpha = 0;
			} else{
				var alpha = (error - minSinglePointError) / (maxSinglePointError - minSinglePointError);
			}
			var colorR = Math.round(alpha * BAD_ERROR_COLOR[0] + (1-alpha) * GOOD_ERROR_COLOR[0])
			var colorG = Math.round(alpha * BAD_ERROR_COLOR[1] + (1-alpha) * GOOD_ERROR_COLOR[1])
			var colorB = Math.round(alpha * BAD_ERROR_COLOR[2] + (1-alpha) * GOOD_ERROR_COLOR[2])
			
			document.getElementById('corr' + i + 'error').style.color = 'rgb(' + colorR + ',' + colorG + ',' + colorB + ')';
		}
	}
}